package com.mspbots.sync.wise.service;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.mspbots.api.DataNotFoundException;
import com.mspbots.sync.BaseGenericService;
import com.mspbots.sync.model.BoardOnly;
import com.mspbots.sync.model.ExceptionLog;
import com.mspbots.sync.model.PriorityOnly;
import com.mspbots.sync.utils.WiseParamsUtils;
import com.mspbots.sync.wise.client.ClientProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.stereotype.Service;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ObjectUtils;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;

import java.time.Duration;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;


/**
 * com.mspbots.sync.wise.service.BoardService
 *
 * @author Alex bob(https://github.com/vnobo)
 * @date Created by 2019/11/13
 */
@Log4j2
@Service
@RequiredArgsConstructor
public class BoardService extends BaseGenericService implements BaseSyncService {

    public Flux<BoardOnly> getBoards(ClientProperties clientProperties) {
        MultiValueMap<String, String> params = WiseParamsUtils.paginationBuild("1", null);
        params.set("orderBy", "id");
        return this.cwWebClient.fetch("/service/boards/", params)
                .switchMap(jsonNode -> {
                    if (ObjectUtils.isEmpty(jsonNode.get("id"))) {
                        log.error("This tenant sync BOARDS error.msg: {}", jsonNode);
                        this.messagingTemplate.convertAndSend("exception.log", ExceptionLog.builder()
                                .tenantId(clientProperties.getTenantId()).name("Connect wise")
                                .status(500).type("SYNC-WISE-BOARDS").msg("Connect wise sync BOARDS entry error.")
                                .extend(jsonNode).build());
                        return Flux.empty();
                    }
                    return Flux.just(jsonNode);
                })
                .switchIfEmpty(Mono.error(new DataNotFoundException("This tenant " + clientProperties.getTenantId() + " get boards is empty.")))
                .map(jsonNode -> BoardOnly.builder().tenantId(clientProperties.getTenantId())
                        .cwId(jsonNode.get("id").asInt())
                        .name((jsonNode.get("projectFlag").asBoolean() ? "Project: " : "") + jsonNode.get("name").asText())
                        .updateTime(LocalDateTime.parse(jsonNode.findValue("lastUpdated").asText(), DateTimeFormatter.ISO_DATE_TIME))
                        .inactiveFlag(!ObjectUtils.isEmpty(jsonNode.findValue("inactiveFlag")) && jsonNode.findValue("inactiveFlag").asBoolean()).build())
                .flatMap(boardOnly -> this.getStatuses(boardOnly.getCwId())
                        .map(jsonNode -> {
                            ObjectNode objectNode = ObjectUtils.isEmpty(boardOnly.getExtend())
                                    ? this.objectMapper.createObjectNode() : boardOnly.getExtend().deepCopy();
                            objectNode.set("status", jsonNode);
                            boardOnly.setExtend(objectNode);
                            return boardOnly;
                        }))
                .flatMap(boardOnly -> this.getSubtypes(boardOnly.getCwId())
                        .map(jsonNode -> {
                            ObjectNode objectNode = ObjectUtils.isEmpty(boardOnly.getExtend())
                                    ? this.objectMapper.createObjectNode() : boardOnly.getExtend().deepCopy();
                            objectNode.set("subtypes", jsonNode);
                            boardOnly.setExtend(objectNode);
                            return boardOnly;
                        }))
                .flatMap(boardOnly -> this.getTypes(boardOnly.getCwId())
                        .map(jsonNode -> {
                            ObjectNode objectNode = ObjectUtils.isEmpty(boardOnly.getExtend())
                                    ? this.objectMapper.createObjectNode() : boardOnly.getExtend().deepCopy();
                            objectNode.set("types", jsonNode);
                            boardOnly.setExtend(objectNode);
                            return boardOnly;
                        }))
                .flatMap(boardOnly -> this.getItems(boardOnly.getCwId())
                        .map(jsonNode -> {
                            ObjectNode objectNode = ObjectUtils.isEmpty(boardOnly.getExtend())
                                    ? this.objectMapper.createObjectNode() : boardOnly.getExtend().deepCopy();
                            objectNode.set("items", jsonNode);
                            boardOnly.setExtend(objectNode);
                            return boardOnly;
                        }))
                .flatMap(boardOnly -> this.getExcludedMembers(boardOnly.getCwId())
                        .map(jsonNode -> {
                            ObjectNode objectNode = ObjectUtils.isEmpty(boardOnly.getExtend())
                                    ? this.objectMapper.createObjectNode() : boardOnly.getExtend().deepCopy();
                            objectNode.set("excludedMembers", jsonNode);
                            boardOnly.setExtend(objectNode);
                            return boardOnly;
                        }));
    }

    public Flux<JsonNode> getAssociations(ClientProperties clientProperties,Integer boardId) {
        MultiValueMap<String, String> params = WiseParamsUtils.paginationBuild("1", null);
        params.set("orderBy", "id");
        return this.cwWebClient.fetch("/service/boards/" + boardId + "/typeSubTypeItemAssociations", params)
                .subscriberContext(ctx -> ctx.put("properties", clientProperties))
                .switchMap(jsonNode -> {
                    if (ObjectUtils.isEmpty(jsonNode.get("id"))) {
                        log.error("This tenant sync Associations error.msg: {}", jsonNode);
                        this.messagingTemplate.convertAndSend("exception.log", ExceptionLog.builder()
                                .tenantId(clientProperties.getTenantId()).name("Connect wise")
                                .status(500).type("SYNC-WISE-BOARD-ASSOCIATIONS").msg("Connect wise sync board Associations  error.")
                                .extend(jsonNode).build());
                        return Flux.empty();
                    }
                    return Flux.just(jsonNode);
                });

    }

    public Flux<PriorityOnly> getPriorities(ClientProperties clientProperties) {
        MultiValueMap<String, String> params = WiseParamsUtils.paginationBuild("1", null);
        params.set("orderBy", "id");
        return this.cwWebClient.fetch("/service/priorities", params)
                .switchMap(jsonNode -> {
                    if (ObjectUtils.isEmpty(jsonNode.get("id"))) {
                        log.error("This tenant sync PRIORITIES error.msg: {}", jsonNode);
                        this.messagingTemplate.convertAndSend("exception.log", ExceptionLog.builder()
                                .tenantId(clientProperties.getTenantId()).name("Connect wise")
                                .status(500).type("SYNC-WISE-PRIORITIES").msg("Connect wise sync PRIORITIES entry error.")
                                .extend(jsonNode).build());
                        return Flux.empty();
                    }
                    this.messagingTemplate.convertAndSend("exception.log", ExceptionLog.builder()
                            .tenantId(clientProperties.getTenantId()).name("Connect wise")
                            .status(200).type("SYNC-WISE-PRIORITIES").msg("Connect wise sync PRIORITIES entry success.")
                            .build());
                    return Flux.just(jsonNode);
                })
                .switchIfEmpty(Mono.error(new DataNotFoundException("This tenant " + clientProperties.getTenantId() + " get priorities empty.")))
                .map(jsonNode -> PriorityOnly.builder().tenantId(clientProperties.getTenantId())
                        .cwId(jsonNode.get("id").asLong()).name(jsonNode.get("name").asText()).color(jsonNode.get("color").asText()).build());

    }

    public Mono<JsonNode> getStatuses(Integer boardId) {
        MultiValueMap<String, String> params = WiseParamsUtils.paginationBuild("1", "inactive=false");
        params.set("orderBy", "id");
        return super.cwWebClient.getRepository("/service/boards/" + boardId + "/statuses", params)
                .defaultIfEmpty(this.objectMapper.createObjectNode());

    }

    public Mono<JsonNode> getSubtypes(Integer boardId) {
        MultiValueMap<String, String> params = WiseParamsUtils.paginationBuild("1", "inactiveFlag=false");
        params.set("orderBy", "id");
        return cwWebClient.getRepository("/service/boards/" + boardId + "/subtypes", params)
                .defaultIfEmpty(this.objectMapper.createObjectNode());

    }

    public Mono<JsonNode> getTypes(Integer boardId) {
        MultiValueMap<String, String> params = WiseParamsUtils.paginationBuild("1", "inactiveFlag=false");
        params.set("orderBy", "id");
        return cwWebClient.getRepository("/service/boards/" + boardId + "/types", params)
                .defaultIfEmpty(this.objectMapper.createObjectNode());

    }

    public Mono<JsonNode> getItems(Integer boardId) {
        MultiValueMap<String, String> params = WiseParamsUtils.paginationBuild("1", "inactiveFlag=false");
        params.set("orderBy", "id");
        return cwWebClient.getRepository("/service/boards/" + boardId + "/items", params)
                .defaultIfEmpty(this.objectMapper.createObjectNode());

    }

    public Mono<JsonNode> getExcludedMembers(Integer boardId) {
        MultiValueMap<String, String> params = WiseParamsUtils.paginationBuild("1", null);
        params.set("orderBy", "id");
        return cwWebClient.getRepository("/service/boards/" + boardId + "/excludedMembers", params)
                .defaultIfEmpty(this.objectMapper.createObjectNode());
    }


    @Override
    public void autoSync(ClientProperties clientProperties) {
        this.getBoards(clientProperties)
                .subscriberContext(ctx -> ctx.put("properties", clientProperties))
                .doFirst(() -> this.messagingTemplate.convertAndSend("exception.log", ExceptionLog.builder()
                        .tenantId(clientProperties.getTenantId()).name("Connect wise")
                        .status(200).type("SYNC-WISE-BOARDS").msg("Connect wise sync CONTACTS entry success.")
                        .build()))
                .delayElements(Duration.ofMillis(500))
                .doOnNext(boardOnly -> this.syncAssociations(clientProperties,boardOnly.getCwId()))
                .delayElements(Duration.ofMillis(500))
                .doOnNext(boardOnly -> this.syncPriority(clientProperties))
                .subscribeOn(Schedulers.elastic())
                .subscribe(boardOnly -> {
                    log.debug("Sync wise boardOnly save success. id: {}", boardOnly.getCwId());
                    this.messagingTemplate.convertAndSend("wise.board", boardOnly);
                }, error -> log.error("Connect wise SYNC-WISE-BOARD error,msg:{}", error.getMessage()));
    }

    private void syncAssociations(ClientProperties clientProperties,Integer boardId) {
        this.getAssociations(clientProperties,boardId)
                .subscriberContext(ctx -> ctx.put("properties", clientProperties))
                .doFirst(() -> this.messagingTemplate.convertAndSend("exception.log", ExceptionLog.builder()
                        .tenantId(clientProperties.getTenantId()).name("Connect wise")
                        .status(200).type("SYNC-WISE-BOARD-ASSOCIATIONS").msg("Connect wise sync board type subtype items  success.")
                        .build()))
                .subscribeOn(Schedulers.elastic())
                .subscribe(resultNode -> {
                    ObjectNode entryNode = resultNode.deepCopy();
                    entryNode.put("tenantId", clientProperties.getTenantId());
                    entryNode.put("boardId", boardId);
                    log.debug("Sync wise board type subtype items save success. id: {}", resultNode.get("id"));
                    this.messagingTemplate.convertAndSend("wise.board.associations", entryNode);
                }, error -> log.error("Connect wise SYNC-WISE-BOARD-ASSOCIATIONS error,msg:{}", error.getMessage()));
    }

    private void syncPriority(ClientProperties clientProperties) {
        this.getPriorities(clientProperties)
                .subscriberContext(ctx -> ctx.put("properties", clientProperties))
                .subscribeOn(Schedulers.elastic())
                .subscribe(priorityOnly -> {
                    log.debug("Sync wise boardOnly save success. id: {}", priorityOnly.getCwId());
                    this.messagingTemplate.convertAndSend("wise.priority", priorityOnly);
                }, error -> log.error("Connect wise SYNC-WISE-PRIORITY error,msg:{}", error.getMessage()));
    }
}
